using System;
using System.Collections.Generic;
using System.Timers;
using System.Linq;
using System.Threading.Tasks;
using System.Collections.Concurrent;

namespace HPM.Core
{
	public class Simulation
	{
		#region Private Properties
		private Timer Clock { get; set; }
		
		private ConcurrentBag<Person> Persons { get; set; }

		private List<Purpose> AllPurposes { get; set; }
		
		//TODO: consider array and array.resize and/or indexes.concat and addrange
		private List<int> Indexes { get; set; } //data used in aggregate functions
		#endregion
		
		#region Readonly Public Properties
		private DateTime _Time { get; set; }

		public DateTime Time { get { return _Time; } }
		
		private bool _Running { get; set; }

		public bool Running { get { return _Running; } } //bool if sim is running
		#endregion
		
		#region Public Properties
		//TODO: consider messaging variable to display status of internal operations which can be retrieved from external app
		
		//TODO: add comments adds attributes that can be retrieved through serialization rather than static in interface
		public int TimeDilation { get; set; } //difference between observed time and actual time passed in simulation
		
		public int MaxYears { get; set; }
		
		public int TotalPersonPurposes { get; set; } //fixed total purposes each person has

		public int NumberOfPersons { get; set; } //TODO: readonly externally or add on set
		
		public int MaxPossiblePurposes { get; set; } //max of all possible purposes. finite but very large.
		#endregion
		
		#region Instantiation
		public Simulation ()
		{
			Initialize (10000, 10, 9120, 1000000);
		}
		
		public Simulation (int NumberOfPersons)
		{
			//9120 years 95% chance doomsday argument
			//wiki, bls.gov 2011 7-10 jobs age 67*365 higher in prev years, 4*365. world average already accounts for unnatural deaths. only in the US
			//1mil is arbitrary
			Initialize (NumberOfPersons, 10, 9120, 1000000);
		}
		
		public Simulation (int NumberOfPersons, int TotalPersonPurposes, int MaxYears, int MaxPossiblePurposes)
		{
			Initialize (NumberOfPersons, TotalPersonPurposes, MaxYears, MaxPossiblePurposes);
		}
		
		private void Initialize (int NumberOfPersons, int TotalPersonPurposes, int MaxYears, int MaxPossiblePurposes)
		{
			this.TimeDilation = 1000;
			this.Persons = new ConcurrentBag<Person> ();
			this.NumberOfPersons = NumberOfPersons;
			this.TotalPersonPurposes = TotalPersonPurposes;
			this.MaxYears = MaxYears;
			this.MaxPossiblePurposes = MaxPossiblePurposes;
			_Time = new DateTime (1, 1, 1);
		}
		#endregion
		
		#region Functionality
		public void Start ()
		{
			if (!this._Running) {
				GeneratePersons (NumberOfPersons);
              
				Clock = new Timer (TimeDilation);
				Clock.Elapsed += new ElapsedEventHandler (Update);
				Clock.Start ();
			
				this._Running = true;
			}
		}
		
		private void GeneratePersons (int NumberOfPersons)
		{
			//this.Persons = Enumerable.Repeat (new Person (TotalPersonPurposes, MaxPossiblePurposes), NumberOfPersons).ToList ();
			Parallel.For (0, NumberOfPersons, delegate(int i) {
				this.Persons.Add (new Person (TotalPersonPurposes, MaxPossiblePurposes));
			});
			//TODO: consider success status
		}
		
		private void RemovePersons (int NumberOfPersons)
		{
			Parallel.For (0, NumberOfPersons, delegate(int i) {
				Person p;
				this.Persons.TryTake (out p);
				p = null;
			});
			//TODO: consider success status
		}
		
		public void Stop ()
		{
			if (this._Running) {
				Clock.Stop ();
				this._Running = false;
			}
		}
		
		private void Update (object source, ElapsedEventArgs e)
		{
			Clock.Stop ();
			this._Running = false;
			if (_Time.Year < MaxYears) {
				_Time = _Time.AddDays (1);
			
				//TODO: later expand to exponential growth
				Person p = new Person (TotalPersonPurposes, MaxPossiblePurposes); //TODO: consider pass indexes as ref
				//Clock.Elapsed += new ElapsedEventHandler (p.Update);
				
				//sync changes to numberofpersons by user to dynamically increase or decrease population
				if (Persons.Count < this.NumberOfPersons) {
					GeneratePersons (this.NumberOfPersons - Persons.Count);
				}
				if (Persons.Count > this.NumberOfPersons) {
					RemovePersons (Persons.Count - this.NumberOfPersons);
				}
				
				Persons.Add (p);
				this.NumberOfPersons += 1;
				Clock.Interval = TimeDilation;
				Clock.Start ();
				this._Running = true;
			}
		}
		
		//resync index data so all aggregates work off snapshot of data instead of live changing values
		public void SyncIndexes ()
		{
			if (this.Indexes != null) {
				this.Indexes.Clear ();
			} else {
				this.Indexes = new List<int> ();
			}
			foreach (Person pe in Persons) {				
				//Parallel.ForEach(Persons, pe=> {
				foreach (Purpose p in pe.Purposes) {
					this.Indexes.Add (p.Index);
				}
				//});
			}
		}
		#endregion
		
		#region Aggregates
		//TODO: review aggregates for performance improvements through further consolidation instead of reenumeration
		public List<MatchingPurpose> GetMatchingPurposeIds ()
		{	
			if (this.Indexes == null) {
				SyncIndexes ();
			}
			
			return this.Indexes.GroupBy (x => x)
			.Select (g => new MatchingPurpose () {
					Index = g.Key, 
					Count = g.Count ()
				}).Where (x => x.Count > 1).OrderByDescending (x => x.Count).ToList ();
		}
		
		public int GetTotalRecurringPurposes ()
		{
			if (this.Indexes == null) {
				SyncIndexes ();
			}
			
			return this.Indexes.GroupBy (x => x)
			.Select (g => new {
					Count = g.Count ()
				}).Where (x => x.Count > 1).Sum (x => x.Count);
		}
			
		public int GetTotalDistinctPurposes ()
		{
			if (this.Indexes == null) {
				SyncIndexes ();
			}
			
			return this.Indexes.GroupBy (x => x)
			.Select (g => new {
					Count = g.Count ()
				}).Where (x => x.Count == 1).Sum (x => x.Count);
		}
		
		//TODO: parallelize
		//TODO: consider allowing update of total as purposes are added by proving temp sephamore in classes pulled by global sephamore
		public int GetTotalActivePurposes ()
		{
			//should never be greater than total available
//			int i = 0;
//			foreach (Person pe in Persons) {
//			foreach (Purpose p in pe.Purposes) {
//					i+=1; //TODO: change to aggregate
//				}
//			}
//			return i;
			if (this.Indexes == null) {
				SyncIndexes ();
			}
			
			return Indexes.Count ();
		}
		#endregion
		
		public void Save (string FilePath)
		{
			//TODO: add my common xmlserialization.cs file for save to disk
			throw new NotImplementedException ("Not yet implemented");
		}
	}
}

